import os
import signal
from tempfile import NamedTemporaryFile, mkstemp
from threading import Thread, Event
from subprocess import Popen, PIPE
    # TODO: look into the pty module and openpty(), to address some problems we've seen with popen and subprocess
from cloudcontrol.exceptions import NonzeroReturnStatusError
from cloudcontrol._support import bytes_from_stream
from cloudcontrol._support.pickledexec import PickledExecution
from cloudcontrol._connections.base import AbstractConnection
from cloudcontrol._streams import CommandStream, GeneratorStream


class LocalUserConnection(AbstractConnection):
    """connection object that corresponds to the current
    user on the local machine"""
    
    def __init__(self):
        AbstractConnection.__init__(self)
    
    def open_file(self, filepath, mode = "w+"):
        return open(filepath, mode)
    
    def open_tempfile(self):
        filename = mkstemp()[1]
        return self.open_file(filename)
    
    def create_command_stream(self, command):

        pipe = Popen([command], shell = True, close_fds = True, stdout = PIPE, stderr = PIPE, stdin = PIPE)
        done_event = Event()
        
        def watch_stdout():
            
            # yield each line from the stream
            for byte in bytes_from_stream(pipe.stdout):
                yield byte
                
            # raise exceptions on bad return codes
            done_event.wait()
            code = pipe.returncode
            if code != 0:
                raise NonzeroReturnStatusError(code, command)

        def watch_stderr():
            
            # yield each line from the stream
            for byte in bytes_from_stream(pipe.stderr):
                yield byte
                
            # raise exceptions on bad return codes
            done_event.wait()
            code = pipe.returncode
            if code != 0:
                raise NonzeroReturnStatusError(code, command)
        
        # hack to reliably watch for the process to finish
        # (for some reason, pipe.wait() could sometimes fail with no child process)
        def watch_status():
            try:
                if pipe.returncode is None:
                    pipe.wait()
            except OSError, e:
                # TODO: should this do anything? the process is done at this point either way
                pass
            done_event.set()
        
        Thread(target = watch_status).start()
        
        # set up an input handler
        def input_handler(buffer_str):
            pipe.stdin.write( buffer_str )
            
        def kill_handler():
            if pipe.returncode is None:
                os.kill( pipe.pid, signal.SIGKILL )

        # FIXME: somehow needs to register an 'abort' method that cuts off all streams
        return CommandStream(watch_stdout, watch_stderr, input_handler, kill_handler)
        
    def create_generator_stream(self, generator, virtualenv = None):
        
        # create the pickled execution for this generator
        pickled_execution = PickledExecution(generator)
        
        # write the code that must be executed
        temp_code_file = NamedTemporaryFile()
        temp_code_file.write( pickled_execution.code )
        temp_code_file.flush()
        
        # execute it via the shell
        if virtualenv == None:
            python_cmd = "python %s" % temp_code_file.name
        else:
            python_cmd = "%s/bin/python %s" % (virtualenv, temp_code_file.name)
        pipe = Popen([python_cmd], shell = True, close_fds = True, stdout = PIPE, stderr = PIPE, stdin = PIPE)
        
        # asynchronously make sure the temp file is deleted after finishing the command
        def close_tempfile_on_completion():
            pipe.wait()
            temp_code_file.close()
            
        Thread(target = close_tempfile_on_completion).start()
        
        # set up an input handler
        def input_handler(obj):
            pickled_execution.write_object_to_stdin(obj, pipe.stdin)
            
        def kill_handler():
            os.kill( pipe.pid, signal.SIGKILL )
        
        # do the iteration over the resulting python execution
        return GeneratorStream( lambda: pickled_execution.get_response_iterator(pipe.stdout, pipe.stderr), input_handler, kill_handler )


